﻿using IGS.Core.Systems;
using NUnit.Framework;

namespace IGS.Core.Tests.Systems
{
    public static class BodyTests
    {
        [TestFixture]
        public class GivenOneBodyWithInitialConditions
        {
            [Test]
            public void WhenInstantiatedThenLastPositionAndVelocityShouldBeSetToInitialPositionAndVelocity()
            {
                // Arrange
                var position = new Vector(1, 2, 3);
                var velocity = new Vector(3, 4, 5);

                // Act
                var body = new Body(1.0, position, velocity);

                // Assert
                Assert.IsNotNull(body, "Body");
                Assert.AreEqual(position, body.LastPosition, "Body.LastPosition");
                Assert.AreEqual(velocity, body.LastVelocity, "Body.LastVelocity");
            }

            [Test]
            public void WhenKineticEnergyIsAccessedShouldReturnTheNewtonianKineticEnergy()
            {
                // Arrange
                var body = new Body(3, new Vector(), new Vector(2, 4, 6));

                // Act
                var kinetic = body.KineticEnergy;

                // Assert
                Assert.AreEqual(84, kinetic, "Body.KineticEnergy");
            }

            [Test]
            public void WhenPotentialEnergyIsAccessedShouldReturnTheGravitationalPotentialEnergy()
            {
                // Arrange
                var body = new Body(0, new Vector(), new Vector());

                // Act
                var potential = body.PotentialEnergy;

                // Assert
                Assert.AreEqual(0, potential, "Body.PotentialEnergy");
            }
            
            [Test]
            public void WhenResetIsCalledTheAccelerationShouldBeSetToTheZeroVector()
            {
                // Arrange
                var body = new Body(0, new Vector(), new Vector())
                {
                    Acceleration = new Vector(2, 3, 4)
                };

                // Act
                body.Reset();

                // Assert
                Assert.AreEqual(new Vector(0, 0, 0), body.Acceleration, "Body.Acceleration");
            }
            
            [Test]
            public void WhenResetIsCalledThenPotentialShouldBeSetToZero()
            {
                // Arrange
                var body = new Body(0, new Vector(), new Vector())
                {
                    PotentialEnergy = 1.0
                };

                // Act
                body.Reset();

                // Assert
                Assert.AreEqual(0.0, body.PotentialEnergy, "Body.PotentialEnergy01");
            }

            [Test]
            public void WhenEndStepIsCalledThenLastPositionShouldBeSetToCurrentPosition()
            {
                // Arrange
                // ReSharper disable UseObjectOrCollectionInitializer
                var body = new Body(0, new Vector(), new Vector());
                // ReSharper restore UseObjectOrCollectionInitializer
                body.Position = new Vector(1, 2, 3);

                // Act
                body.EndStep();

                // Assert
                Assert.AreEqual(body.LastPosition, body.Position, "Body.LastPosition");
            }

            [Test]
            public void WhenEndStepIsCalledThenLastVelocityShouldBeSetToCurrentVelocity()
            {
                // Arrange
                // ReSharper disable UseObjectOrCollectionInitializer
                var body = new Body(0, new Vector(), new Vector());
                // ReSharper restore UseObjectOrCollectionInitializer
                body.Velocity = new Vector(1, 2, 3);

                // Act
                body.EndStep();

                // Assert
                Assert.AreEqual(body.LastVelocity, body.Velocity, "Body.LastVelocity");
            }

            [Test]
            public void WhenEndStepIsCalledThenLastAccelerationShouldBeSetToCurrentAcceleration()
            {
                // Arrange
                var body = new Body(0, new Vector(), new Vector())
                {
                    Acceleration = new Vector(1, 2, 3)
                };

                // Act
                body.EndStep();

                // Assert
                Assert.AreEqual(body.LastAcceleration, body.Acceleration, "Body.LastVelocity");
            }

            [Test]
            public void WhenToStringIsCalledTheMassPositionAndVelocityShouldBeConvertedToString()
            {
                // Arrange
                var body = new Body(1.0, new Vector(1, 1, 1), new Vector(2, 2, 2));

                // Act
                var result = body.ToString();

                // Assert
                Assert.AreEqual("1 1.000 1.000 1.000 2.000 2.000 2.000", result, "Body.ToString");
            }
        }

        [TestFixture]
        public class GivenTwoBodiesWithInitialConditions
        {
            [Test]
            public void WhenPairwiseIsCalledThenAccelerationShouldBeSetForBothBodies()
            {
                // Arrange
                var body1 = new Body(1.0, new Vector(+0.5, 0, 0), new Vector());
                var body2 = new Body(1.0, new Vector(-0.5, 0, 0), new Vector());

                // Act
                body1.Pairwise(body2);

                // Assert
                Assert.AreEqual(new Vector(-1, 0, 0), body1.Acceleration, "body1.Acceleration");
                Assert.AreEqual(new Vector(+1, 0, 0), body2.Acceleration, "body2.Acceleration");
            }

            [Test]
            public void WhenTheyInteractShouldCalculateGravitationalPotentialEnergyForBothBodies()
            {
                // Arrange
                var body1 = new Body(1.0, new Vector(+0.5, 0, 0), new Vector());
                var body2 = new Body(1.0, new Vector(-0.5, 0, 0), new Vector());

                // Act
                body1.Pairwise(body2);

                // Assert
                Assert.AreEqual(-1.0, body1.PotentialEnergy, "Body1.PotentialEnergy");
                Assert.AreEqual(-1.0, body2.PotentialEnergy, "Body2.PotentialEnergy");
            }
        }
    }
}
